﻿using System;
using System.Diagnostics.Contracts;
using System.Xml;
using XmlStudio.DataLayer;
using XmlStudio.Interfaces;
using XmlStudio.Models.Events;
using XmlStudio.Utilities;

namespace XmlStudio.Models.Actions {
    /// <summary>
    /// Changes the order of the selected node.
    /// </summary>
    internal sealed class ChangeNodeOrderAction : XmlModelAction {
        #region Fields
        #region Public
        #endregion

        #region Private
        /// <summary>
        /// Old previous sibling of moved node.
        /// </summary>
        private BigXmlNodeInternal oldPreviousSibling;

        /// <summary>
        /// Parent node of node to move.
        /// </summary>
        private BigXmlNodeInternal parentNode;

        /// <summary>
        /// Previous sibling of node to move (future).
        /// </summary>
        private BigXmlNodeInternal previousSibling;

        /// <summary>
        /// Node to move.
        /// </summary>
        private BigXmlNodeInternal nodeToMove;
        #endregion
        #endregion

        #region Constructors
        #region Public
        /// <summary>
        /// Initializes a new instance of the <see cref="ChangeNodeOrderAction"/> class.
        /// </summary>
        /// <param name="model">The model.</param>
        /// <param name="previousSibling">Previous sibling to be.</param>
        /// <param name="nodeToMove">Node to move.</param>
        public ChangeNodeOrderAction(IXmlModelWithNotifications model, BigXmlNodeInternal previousSibling, BigXmlNodeInternal nodeToMove)
            : base(model) {
            //Contract.Requires<ArgumentNullException>(xmlModel != null);
            //Contract.Requires<ArgumentNullException>(nodeToMove != null);

            this.parentNode = nodeToMove.Parent as BigXmlNodeInternal;
            this.wasModified = this.parentNode.Modified;
            this.oldPreviousSibling = this.parentNode.GetLeftSibling(nodeToMove) as BigXmlNodeInternal;
            this.previousSibling = previousSibling;
            this.nodeToMove = nodeToMove;
            this.address = this.parentNode.Address.DeepClone<int[]>();
        }
        #endregion

        #region Private
        #endregion
        #endregion

        #region Methods
        #region Public
        #endregion

        #region Protected
        /// <summary>
        /// Executes the action.
        /// </summary>
        protected override void ExecuteCore() {
            if(this.parentNode.XmlNode is XmlNode && this.nodeToMove.XmlNode is XmlNode && (this.previousSibling == null || this.previousSibling.XmlNode is XmlNode)) {
                XmlNode l_node = this.parentNode.XmlNode as XmlNode;
                XmlNode l_nodeToMove = this.nodeToMove.XmlNode as XmlNode;
                this.parentNode.ChildNodes.Remove(nodeToMove);
                l_node.RemoveChild(l_nodeToMove);

                if(previousSibling != null) {
                    this.parentNode.ChildNodes.InsertAfter(nodeToMove, previousSibling);
                    l_node.InsertAfter(l_nodeToMove, previousSibling.XmlNode as XmlNode);
                } else {
                    this.parentNode.ChildNodes.Insert(0, nodeToMove);
                    l_node.PrependChild(l_nodeToMove);
                }

                TreeBuilder.AdjustAddresses(this.parentNode, this.model);
                this.parentNode.SetModified(true);

                this.model.SetModified(true);
                this.model.OnSubtreeUpdated(new SubtreeUpdatedEventArgs<BigXmlNode>(this.parentNode));
                this.model.OnRawXmlChanged(new RawXmlChangedEventArgs());
            } else {
                throw new InvalidOperationException("Current node has wrong type.");
            }
        }

        /// <summary>
        /// Executes the undo of this action.
        /// </summary>
        protected override void UnExecuteCore() {
            if(this.parentNode.XmlNode is XmlNode && this.nodeToMove.XmlNode is XmlNode && (this.oldPreviousSibling == null || this.oldPreviousSibling.XmlNode is XmlNode)) {
                XmlNode l_node = this.parentNode.XmlNode as XmlNode;
                XmlNode l_nodeToMove = this.nodeToMove.XmlNode as XmlNode;

                this.parentNode.ChildNodes.Remove(nodeToMove);
                l_node.RemoveChild(l_nodeToMove);

                if(oldPreviousSibling != null) {
                    this.parentNode.ChildNodes.InsertAfter(nodeToMove, oldPreviousSibling);
                    l_node.InsertAfter(l_nodeToMove, oldPreviousSibling.XmlNode as XmlNode);
                } else {
                    this.parentNode.ChildNodes.Insert(0, nodeToMove);
                    l_node.PrependChild(l_nodeToMove);
                }

                TreeBuilder.AdjustAddresses(this.parentNode, this.model);
                this.parentNode.SetModified(wasModified);

                this.model.SetModified(this.modelWasModified);
                this.model.OnSubtreeUpdated(new SubtreeUpdatedEventArgs<BigXmlNode>(this.parentNode));
                this.model.OnRawXmlChanged(new RawXmlChangedEventArgs());
            }
        }
        #endregion

        #region Private
        #endregion
        #endregion
    }
}